use super::serial::ModeSConn;
use crate::{
    error::{error_of, AppErr},
    utils::{copy, encode_u16, encode_u32, encode_u8, new_bytes, print_buf},
};
use tokio::io::AsyncReadExt;

const HEAD0: u8 = 0xE1;
const HEAD1: u8 = 0x1E;
const END: u8 = 0xEF;

pub mod addr {

    pub const IPC: u8 = 0;
    pub const MASTER: u8 = 1;
    pub const HEAT: u8 = 2;
    pub const WEIGHT: u8 = 3;
    pub const SERVER: u8 = 4;
}

pub mod types {

    pub const ACK: u8 = 0;
    pub const PING: u8 = 1;
    pub const PONG: u8 = 2;
    pub const SESSION: u8 = 3;
    pub const REQ: u8 = 4;
    pub const RES: u8 = 5;
    pub const QUERY: u8 = 6;
    pub const RESULT: u8 = 7;
    pub const NOTIFY: u8 = 8;
    pub const NOTIFY_ACK: u8 = 9;
}

pub mod cmd {

    pub const REQ_OTA_START: u8 = 1;
    pub const REQ_OTA_TRANSLATE: u8 = 2;
    pub const REQ_OTA_COMPLETE: u8 = 3;
    pub const NOTIFY_LOG: u8 = 4;
    pub const QUERY_TASK: u8 = 5;

    pub const WEIGHT_STATUS: u8 = 0x11;
}

fn sum_xor(buf: &[u8]) -> u8 {
    let mut s = 0;
    for v in buf {
        s ^= *v;
    }
    s
}

async fn sync_head(io: &mut ModeSConn) -> Result<usize, AppErr> {
    let mut buf: [u8; 1] = [0; 1];
    let mut flag = false;

    loop {
        let n = io.port.read(&mut buf).await?;
        if n <= 0 {
            return error_of("串口关闭了");
        }

        if flag && (buf[0] == HEAD1) {
            break;
        }

        flag = buf[0] == HEAD0;
    }

    let n = io.port.read(&mut buf).await?;
    if n <= 0 {
        return error_of("串口关闭了");
    }

    Ok(buf[0] as usize)
}

pub async fn read(io: &mut ModeSConn) -> Result<Box<[u8]>, AppErr> {
    let len = sync_head(io).await?;
    if len < 8 {
        return error_of("len < 8");
    }

    let mut buf = new_bytes(len);
    buf[0] = HEAD0;
    buf[1] = HEAD1;
    buf[2] = len as u8;

    io.port.read_exact(&mut buf[3..]).await?;

    let sum = sum_xor(&buf[3..(len - 2)]);
    if sum != buf[len - 2] {
        return error_of("校验和错误");
    }

    if END != buf[len - 1] {
        return error_of("尾部错误");
    }

    Ok(buf)
}

pub fn make_ping(dst: u8) -> Box<[u8]> {
    let mut buf = new_bytes(8);
    buf[0] = HEAD0;
    buf[1] = HEAD1;
    buf[2] = 8;
    buf[3] = addr::IPC;
    buf[4] = dst;
    buf[5] = types::PING;
    buf[6] = sum_xor(&buf[3..6]);
    buf[7] = END;

    buf
}

pub fn make_pong(dst: u8) -> Box<[u8]> {
    let mut buf = new_bytes(8);
    buf[0] = HEAD0;
    buf[1] = HEAD1;
    buf[2] = 8;
    buf[3] = addr::IPC;
    buf[4] = dst;
    buf[5] = types::PONG;
    buf[6] = sum_xor(&buf[3..6]);
    buf[7] = END;

    buf
}

pub fn make_ack(dst: u8, seq: u8) -> Box<[u8]> {
    let mut buf = new_bytes(9);
    buf[0] = HEAD0;
    buf[1] = HEAD1;
    buf[2] = 9;
    buf[3] = addr::IPC;
    buf[4] = dst;
    buf[5] = types::ACK;
    buf[6] = seq;
    buf[7] = sum_xor(&buf[3..7]);
    buf[8] = END;

    buf
}

pub fn make_session(dst: u8, seq: u8) -> Box<[u8]> {
    let mut buf = new_bytes(9);
    buf[0] = HEAD0;
    buf[1] = HEAD1;
    buf[2] = 9;
    buf[3] = addr::IPC;
    buf[4] = dst;
    buf[5] = types::SESSION;
    buf[6] = seq;
    buf[7] = sum_xor(&buf[3..7]);
    buf[8] = END;

    buf
}

pub fn make_query(dst: u8, cmd: u8) -> Box<[u8]> {
    let mut buf = new_bytes(9);
    buf[0] = HEAD0;
    buf[1] = HEAD1;
    buf[2] = 9;
    buf[3] = addr::IPC;
    buf[4] = dst;
    buf[5] = types::QUERY;
    buf[6] = cmd;
    buf[7] = sum_xor(&buf[3..7]);
    buf[8] = END;

    buf
}

pub fn make_req(buf: &mut [u8], dst: u8, seq: u8, cmd: u8) {
    let data_len = buf.len() - 10;
    buf[0] = HEAD0;
    buf[1] = HEAD1;
    buf[2] = buf.len() as u8;
    buf[3] = addr::IPC;
    buf[4] = dst;
    buf[5] = types::REQ;
    buf[6] = seq;
    buf[7] = cmd;
    buf[data_len + 8] = sum_xor(&buf[3..(8 + data_len)]);
    buf[data_len + 9] = END;
}

pub fn make_request(dst: u8, seq: u8, cmd: u8, data: &[u8]) -> Box<[u8]> {
    let len = (10 + data.len()) as usize;
    let mut buf = new_bytes(len);
    copy(&mut buf[8..], data);
    make_req(&mut buf, dst, seq, cmd);
    buf
}

pub fn make_ota_start(buf: &mut [u8], dst: u8, seq: u8, pkg_size: u32) -> usize {
    encode_u32(&mut buf[8..], pkg_size);
    make_req(&mut buf[..10 + 4], dst, seq, cmd::REQ_OTA_START);
    10 + 4
}

pub fn make_ota_translate(buf: &mut [u8], dst: u8, seq: u8, id: u16, data: &[u8]) -> usize {
    encode_u16(&mut buf[8..], id);
    encode_u8(&mut buf[10..], data.len() as u8);
    copy(&mut buf[11..], data);
    make_req(
        &mut buf[..(10 + 2 + 1 + data.len())],
        dst,
        seq,
        cmd::REQ_OTA_TRANSLATE,
    );
    10 + 2 + 1 + data.len()
}

pub fn make_ota_complete(buf: &mut [u8], dst: u8, seq: u8, data: &[u8]) -> usize {
    let hash = md5::compute(data).0;

    encode_u8(&mut buf[8..], 16);
    copy(&mut buf[9..], &hash);
    make_req(&mut buf[..10 + 1 + 16], dst, seq, cmd::REQ_OTA_COMPLETE);

    10 + 1 + 16
}

